Skip to content

Conversation

Scooletz
Copy link
Contributor

@Scooletz Scooletz commented Jul 21, 2025

This PR replaces the copying behavior of JsonRpcMessage.Converter.Read with a single pass deserialization of it. It uses a Union struct that deserializes the raw content of JsonRpcMessage to a bag of properties. Then it uses it to check the conditions and properly construct underlying objects. This makes deserialization ~50% faster and also reduced the allocations.

Motivation and Context

I noticed the double deserialization in JsonRpcMessage.Converter.Read and wanted to make it faster.

How Has This Been Tested?

Benchmarks. Allocations are ~halfed.

Before

Method Mean Error StdDev Gen0 Allocated
DeserializeRequest 1.113 us 0.0167 us 0.0148 us 0.0572 720 B
DeserializeNotification 1.044 us 0.0080 us 0.0062 us 0.0515 664 B
DeserializeResponse 1.053 us 0.0057 us 0.0048 us 0.0515 648 B
DeserializeError 1.333 us 0.0138 us 0.0129 us 0.0954 1200 B

After

Method Mean Error StdDev Gen0 Allocated
DeserializeRequest 528.4 ns 9.85 ns 9.67 ns 0.0324 408 B
DeserializeNotification 443.9 ns 6.79 ns 6.35 ns 0.0305 384 B
DeserializeResponse 472.4 ns 6.77 ns 6.00 ns 0.0291 368 B
DeserializeError 732.5 ns 7.05 ns 6.59 ns 0.0687 864 B

Using JsonDocument.Deserialize

The attempt in using doc.Deserialize(options.GetTypeInfo<TMessageType>()) for deserializing

Method Mean Error StdDev Gen0 Allocated
DeserializeRequest 1,147.8 ns 26.56 ns 72.70 ns 0.0458 592 B
DeserializeNotification 963.7 ns 7.17 ns 6.71 ns 0.0420 544 B
DeserializeResponse 987.4 ns 8.92 ns 7.91 ns 0.0439 552 B
DeserializeError 1,289.0 ns 13.14 ns 12.29 ns 0.0820 1048 B

Breaking Changes

Nope

Types of changes

  • Optimization
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@Scooletz Scooletz mentioned this pull request Jul 22, 2025
9 tasks
@Scooletz
Copy link
Contributor Author

@eiriktsarpalis Could you comment on the perf findings? It looks like it's worth it?

@eiriktsarpalis
Copy link
Member

@Scooletz I think we can keep your changes as-is, on the understanding that this only touches the core JSON-RPC infrastructure and is therefore unlikely to require evolution in the future.

/// <summary>
/// The union to deserialize.
/// </summary>
internal struct Union
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Scooletz I would recommend marking the Union struct as private and hand-craft a static Parse(ref Utf8JsonReader reader) that populates an instance of the type. This should further improve performance while also minimizing the amount of source generated code (aka there wouldn't exist any serialization code for the type).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants